package com.wing.oauth2.config;

import cn.binarywang.wx.miniapp.api.WxMaService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wing.common.utils.JsonResult;
import com.wing.common.utils.Result;
import com.wing.oauth2.exception.CabAuthException;
import com.wing.oauth2.exception.LoginAuthException;
import com.wing.oauth2.exception.VersionException;
import com.wing.oauth2.extension.warehouse.WarehouseAuthenticationProvider;
import com.wing.oauth2.filter.*;
import com.wing.oauth2.exception.ValidateCodeException;
import com.wing.oauth2.extension.mobile.SmsCodeAuthenticationProvider;
import com.wing.oauth2.extension.wechat.WechatAuthenticationProvider;
import com.wing.oauth2.details.clientdetails.ClientDetailsServiceImpl;
import com.wing.user.feign.UserClient;
import com.wing.warehouse.feign.WarehouseUserClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

/**
 * SpringSecurity配置类
 *
 * @Author
 */
@Configuration
@EnableWebSecurity
@Slf4j
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService sysUserDetailsService;
    private final UserDetailsService memberUserDetailsService;
    private final ClientDetailsServiceImpl clientDetailsService;
    private final WxMaService wxMaService;
    private final UserClient userClient;
    private final WarehouseUserClient warehouseUserClient;
    private final StringRedisTemplate redisTemplate;
    private final ValidateCodeFilter validateCodeFilter;
    private final CabAuthFilter cabAuthFilter;
    private final LoginAuthFilter loginAuthFilter;

    private final VersionFilter versionFilter;


    /**
     * 配置认证管理器
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 这里是对认证管理器的添加配置
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth
                .authenticationProvider(smsCodeAuthenticationProvider())
                .authenticationProvider(wechatAuthenticationProvider())
                .authenticationProvider(warehouseAuthenticationProvider())
                .authenticationProvider(daoAuthenticationProvider());
    }

    /**
     * 手机验证码认证授权提供者
     *
     * @return
     */
    @Bean
    public SmsCodeAuthenticationProvider smsCodeAuthenticationProvider() {
        SmsCodeAuthenticationProvider provider = new SmsCodeAuthenticationProvider();
        provider.setUserDetailsService(memberUserDetailsService);
        provider.setRedisTemplate(redisTemplate);
        return provider;
    }

    /**
     * 微信认证授权提供者
     *
     * @return
     */
    @Bean
    public WechatAuthenticationProvider wechatAuthenticationProvider() {
        WechatAuthenticationProvider provider = new WechatAuthenticationProvider();
        provider.setUserDetailsService(memberUserDetailsService);
        provider.setWxMaService(wxMaService);
        provider.setUserClient(userClient);
        return provider;
    }

    @Bean
    public WarehouseAuthenticationProvider warehouseAuthenticationProvider() {
        WarehouseAuthenticationProvider provider = new WarehouseAuthenticationProvider();
        provider.setUserDetailsService(memberUserDetailsService);
        provider.setWarehouseUserClient(warehouseUserClient);
        return provider;
    }


    /**
     * 用户名密码认证授权提供者
     *
     * @return
     */
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(sysUserDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        // 是否隐藏用户不存在异常，默认:true-隐藏；false-抛出异常；
        provider.setHideUserNotFoundExceptions(false);
        return provider;
    }


    /**
     * 配置密码加密对象（解密时会用到PasswordEncoder的matches判断是否正确）
     * 用户的password和客户端clientSecret用到，所以存的时候存该bean encode过的密码
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    // @Override
    // public void configure(WebSecurity web) throws Exception {
    //     web.ignoring().antMatchers( "/admin/assets/**", "/admin/components/**", "/admin/file/get/**");
    // }

    /**
     *  安全请求配置,这里配置的是security的部分，这里配置全部通过，安全拦截在资源服务的配置文件中配置，
     *  要不然访问未验证的接口将重定向到登录页面，前后端分离的情况下这样并不友好，无权访问接口返回相关错误信息即可
     * @param http
     * @return void
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 添加验证码校验过滤器
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(cabAuthFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(loginAuthFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(versionFilter,UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                .successHandler(this.loginSuccessHandler())
                .failureHandler(this.loginFailureHandler())
                .permitAll()
                .and().logout().permitAll().logoutSuccessHandler(this.logoutSuccessHandler())
                .and().authorizeRequests()
                .anyRequest().permitAll()
                .and().csrf().disable().cors()
                .and().addFilterAt(customBasicAuthenticationFilter(), BasicAuthenticationFilter.class);
    }


    @Bean
    public CustomBasicAuthenticationFilter customBasicAuthenticationFilter() {
        CustomBasicAuthenticationFilter filter = new CustomBasicAuthenticationFilter();
        filter.setClientService(clientDetailsService);
        filter.setPasswordEncoder(passwordEncoder());
        return filter;
    }


    /**
     * 登录成功处理
     *
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler loginSuccessHandler() {
        return (request, response, authentication) -> {
            String json = handleResponse(JsonResult.success("登录成功"));
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().print(json);
        };
    }

    /**
     * 登录失败处理
     *
     * @return
     */
    @Bean
    public AuthenticationFailureHandler loginFailureHandler() {
        return (request, response, exception) -> {

            String msg = JsonResult.Code.FAIL.getDesc();
            if (exception instanceof UsernameNotFoundException){
                msg = "用户名不存在";
            }
            if (exception instanceof LockedException){
                msg = "用户被冻结";
            }
            if (exception instanceof BadCredentialsException){
                msg = "密码错误";
            }
            if (exception instanceof ValidateCodeException||exception instanceof CabAuthException||exception instanceof LoginAuthException||exception instanceof VersionException){
                msg = exception.getMessage();
            }

            String json = handleResponse(JsonResult.fail(msg));
            if (exception instanceof VersionException){
                json=handleResponse(JsonResult.fail(Result.Code.REQUIRE_VERSION_UPDATE.getCode(),msg));
            }
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().print(json);
        };
    }

    /**
     * 登出成功处理
     *
     * @return
     */
    private LogoutSuccessHandler logoutSuccessHandler() {
        return (request, response, authentication) -> {
            String json = handleResponse(JsonResult.success());
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().print(json);
        };
    }

    private String handleResponse(JsonResult jsonResult) throws JsonProcessingException {
        return new ObjectMapper().writeValueAsString(jsonResult);
    }
}
